/*
* Libresweeper By Loveaabb <https://gitee.com/loveaabb>
* -- msweeper.h --
* 许可证：MIT
*/

#pragma once

#ifndef MSWEEPER_H_INCLUDED
#define MSWEEPER_H_INCLUDED

#include <ctime>
#include <cstdlib>
#include <queue>
#include <string>

#include "curses.h"

#define MINESYMB '*'
#define FLAGSYMB 'P'
#define UNREVSYMB '#'
#define SAFESYMB ' '

#define FWBB 9
#define MINEC 10
#define CUR 11
#define FLAGGED 12
#define BLUEVER 114

typedef char Color;

enum TileType{SAFE=0, MINE};
enum Stat{
	MALLOC_FAILED=0,
	GOOD,
	OUTOFRANGE,
	SAFESTEPPED,
	MINESTEPPED,
	TARGET_ALREADY_REVEALED
	};

struct Coord
{
	int x;
	int y;
};

int randint(int min, int max);

class MapTile
{
public:
	int nearMines;
	bool isFlagged;
	bool isRevealed;
	TileType type;

	MapTile();
//	~MapTile();
	Stat toggle_flag();
	Stat copyto(MapTile& target);
//	Stat count_near_mines();
}; // MapTile


class Map
{
private:
	MapTile* gameMap;
	int width;
	int height;
	int flaggedNum;
	int mineNum;
	int revealedNum;
	int cursorx;
	int cursory;

public:
	Map(int wd, int ht, int minenum);
	~Map();
	int get_width();
	int get_height();
	int get_flagged();
	int get_mineNum();
	int get_revealedNum();
	int getcx();
	int getcy();
	Stat generate_map();
	Stat reveal(int x, int y);
	Stat toggle_flag(int x, int y);
	Stat cursor_move(char dir);
	bool isFinished();
//	Stat toString(string& target);
	Stat print(int cx, int cy, bool isDisplayMines);
}; // Map


// implementation of MapTile BEGIN

MapTile::MapTile()
{
	this->isFlagged = 0;
	this->nearMines = 0;
	this->type = SAFE;
	this->isRevealed = false;
}

Stat MapTile::toggle_flag()
{
	this->isFlagged = !this->isFlagged;
	return GOOD;
}

Stat MapTile::copyto(MapTile& target)
{
	target.isFlagged = isFlagged;
	target.nearMines = nearMines;
	target.type = type;

	return GOOD;
}

// implementation of MapTile END

// -------------------------------------------------------------------

// implementation of Map BEGIN

Map::Map(int wd, int ht, int minenum)
{

	this->width = wd;
	this->height = ht;
	this->gameMap = new MapTile[wd * ht];
	this->mineNum = minenum;
	this->revealedNum = 0;
	this->flaggedNum = 0;
	this->cursorx = 0;
	this->cursory = 0;

	if (!gameMap) exit(114514);

	if (minenum > wd * ht - 1)
	{
		this->mineNum = wd * ht - 1;
	}

	init_pair(1, COLOR_BLUE, COLOR_BLACK);
	init_pair(2, COLOR_GREEN, COLOR_BLACK);
	init_pair(3, COLOR_RED, COLOR_BLACK);
	init_pair(4, COLOR_CYAN, COLOR_BLACK);
	init_pair(5, COLOR_YELLOW, COLOR_BLACK);
	init_pair(6, COLOR_MAGENTA, COLOR_BLACK);
	init_pair(7, COLOR_BLUE, COLOR_BLACK);
	init_pair(8, COLOR_RED, COLOR_BLACK);
	init_pair(FWBB, COLOR_WHITE, COLOR_BLACK);
	init_pair(MINEC, COLOR_YELLOW, COLOR_RED);
	init_pair(FLAGGED, COLOR_WHITE, COLOR_BLACK);
	init_pair(1+BLUEVER, COLOR_BLUE, COLOR_BLUE);
	init_pair(2+BLUEVER, COLOR_GREEN, COLOR_BLUE);
	init_pair(3+BLUEVER, COLOR_RED, COLOR_BLUE);
	init_pair(4+BLUEVER, COLOR_CYAN, COLOR_BLUE);
	init_pair(5+BLUEVER, COLOR_YELLOW, COLOR_BLUE);
	init_pair(6+BLUEVER, COLOR_MAGENTA, COLOR_BLUE);
	init_pair(7+BLUEVER, COLOR_BLUE, COLOR_BLUE);
	init_pair(8+BLUEVER, COLOR_RED, COLOR_BLUE);
	init_pair(FWBB+BLUEVER, COLOR_WHITE, COLOR_BLUE);
	init_pair(MINEC+BLUEVER, COLOR_YELLOW, COLOR_BLUE);
	init_pair(FLAGGED+BLUEVER, COLOR_WHITE, COLOR_BLUE);
}

Map::~Map()
{
	delete[] this->gameMap;
}

int Map::get_flagged()
{
	return this->flaggedNum;
}

int Map::get_height()
{
	return this->height;
}

int Map::get_width()
{
	return this->width;
}

int Map::get_mineNum()
{
	return this->mineNum;
}

int Map::get_revealedNum()
{
	return this->revealedNum;
}


int Map::getcx()
{
	return cursorx;
}

int Map::getcy()
{
	return cursory;
}

Stat Map::cursor_move(char dir)
{
	switch (dir)
	{
		case 's':
			if (cursory < height - 1) cursory++;
			break;
		case 'w':
			if (cursory > 0) cursory--;
			break;
		case 'a':
			if (cursorx > 0) cursorx--;
			break;
		case 'd':
			if (cursorx < width - 1) cursorx++;
			break;
	}

	return GOOD;
}

Stat Map::generate_map()
{
	int i, rannum, xi, yi, xj, yj, nmine;
	MapTile tmp;

	revealedNum = 0;
	flaggedNum = 0;

	for (i = 0; i < width * height; i++)
	{
		if (i < mineNum)
		{
			gameMap[i].type = MINE;
			gameMap[i].isRevealed = false;
			gameMap[i].isFlagged = false;
		}
		else
		{
			gameMap[i].type = SAFE;
			gameMap[i].isRevealed = false;
			gameMap[i].isFlagged = false;
		}
	}

	for (i = 0; i < mineNum; i++)
	{
		rannum = randint(i, width * height - 1);

		gameMap[i].copyto(tmp);
		gameMap[rannum].copyto(gameMap[i]);
		tmp.copyto(gameMap[rannum]);
	}

//	for (i = width * height - 2; i >= fry * width + frx; i--)
//	{
//		gameMap[i].copyto(gameMap[i+1]);
//	}
//	gameMap[fry * width + frx].type = SAFE;
//	gameMap[fry * width + frx].isRevealed = false;
//	gameMap[fry * width + frx].isFlagged = false;

	for (yi = 0; yi < height; yi++)
	{
		for (xi = 0; xi < width; xi++)
		{
			nmine = 0;
			for (xj = xi - 1; xj <= xi + 1; xj++)
			{
				for (yj = yi - 1; yj <= yi + 1; yj++)
				{
					if (xj >= 0 && xj < width && yj >= 0 && yj < height)
					{
						if (gameMap[yj * width + xj].type == MINE)
						{
							nmine++;
						}
					}
				}
			}
			gameMap[yi * width + xi].nearMines = nmine;
		}
	}

	return GOOD;
}

Stat Map::reveal(int x, int y)
{
	std::queue<Coord> revQueue;
	Coord cur, topush;
	int xi, yi;

	if (gameMap[y * width + x].type == MINE)
	{
		return MINESTEPPED;
	}

	if (gameMap[y * width + x].isRevealed)
	{
		return SAFESTEPPED;
	}
	topush.x = x;
	topush.y = y;
	revQueue.push(topush);
	while (!revQueue.empty())
	{
		cur = revQueue.front();
		if (gameMap[cur.y * width + cur.x].nearMines == 0)
		{
			for (yi = cur.y - 1; yi <= cur.y + 1; yi++)
			{
				for (xi = cur.x - 1; xi <= cur.x + 1; xi++)
				{
					if (xi >= 0
							&& xi < width
							&& yi >= 0
							&& yi < height
							&& !(gameMap[yi * width + xi].isFlagged)
							&& !(gameMap[yi * width + xi].isRevealed)
							&& !(xi == cur.x && yi == cur.y))
					{
						gameMap[yi * width + xi].isRevealed = true;
						if (gameMap[yi * width + xi].isFlagged)
						{
							flaggedNum--;
						}
						revealedNum++;

						topush.x = xi;
						topush.y = yi;
						revQueue.push(topush);
					}
				}
			}
		}
		if (!gameMap[cur.y * width + cur.x].isRevealed)
		{
			gameMap[cur.y * width + cur.x].isRevealed = true;
			if (gameMap[cur.y * width + cur.x].isFlagged)
			{
				flaggedNum--;
			}
			revealedNum++;
		}
		revQueue.pop();
	}

	return SAFESTEPPED;
}

Stat Map::toggle_flag(int x, int y)
{
	if (!gameMap[y * width + x].isRevealed)
	{
		if (gameMap[y * width + x].isFlagged)
		{
			flaggedNum--;
		}
		else
		{
			flaggedNum++;
		}
		gameMap[y * width + x].toggle_flag();
	}
	else
	{
		return TARGET_ALREADY_REVEALED;
	}

	return GOOD;
}

bool Map::isFinished()
{
	return (revealedNum == width * height - mineNum);
}

Stat Map::print(int cx, int cy, bool isDisplayMines)
{
//	static chtype colors[] = {
//		COLOR_WHITE,
//		COLOR_BLUE,
//		COLOR_GREEN,
//		COLOR_RED,
//		COLOR_CYAN,
//		COLOR_YELLOW,
//		COLOR_MAGENTA,
//		COLOR_BLUE,
//		COLOR_RED
//		};
	int xi, yi;
	const MapTile* curTile;
	chtype curch;
	chtype curcolor;

	move(cy, cx);

	for (yi = 0; yi < height; yi++)
	{
		for (xi = 0; xi < width; xi++)
		{
			curTile = &gameMap[yi * width + xi];

			if (curTile->isRevealed)
			{
				if (curTile->nearMines == 0)
				{
					curcolor = FWBB;
					curch = SAFESYMB;
				}
				else
				{
					curcolor = curTile->nearMines;
					curch = curTile->nearMines + '0';
				}
			}
			else
			{
				if (curTile->type == MINE)
				{
					if (isDisplayMines)
					{
						curcolor = MINEC;
						curch = MINESYMB;
					}
					else
					{
						if (curTile->isFlagged)
						{
							curcolor = FLAGGED;
							curch = FLAGSYMB;
						}
						else
						{
							curcolor = FWBB;
							curch = UNREVSYMB;
						}
					}
				}
				else
				{
					if (curTile->isFlagged)
					{
						curcolor = FLAGGED;
						curch = FLAGSYMB;
					}
					else
					{
						curcolor = FWBB;
						curch = UNREVSYMB;
					}
				}
			}

			if (xi == cursorx && yi == cursory)
			{
				curcolor += BLUEVER;
			}
			else
			{
//				curbg = COLOR_BLACK;
			}

			attron(COLOR_PAIR(curcolor));
			addch(curch);
			attroff(COLOR_PAIR(curcolor));
		}

		move(cy + yi + 1, cx);
	}

	return GOOD;
}

// implementation of Map END

// -------------------------------------------------------------------

// imp of functions BEGIN

int randint(int min, int max)
{
	return (min + rand() % (max - min + 1));
}

// imp of functions END
#endif // MSWEEPER_H_INCLUDED
